package com.ldn.common.basic.utils.system;

import cn.hutool.core.date.DateTime;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import cn.hutool.crypto.symmetric.DES;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * DataUtils类提供了一系列与数据处理相关的实用工具类
 *
 * @author 李大牛家的牛 (scafel@163.com)
 * @data 2025/1/14 - 14:39
 **/
@Slf4j
public class DataUtils {

    /**
     * 生成带有前后缀和随机数的代码。
     * <p>该方法首先生成当前时间的字符串，然后生成一个随机数，最后将前缀、时间、后缀和随机数组合在一起形成一个唯一的代码。
     *
     * @param prefix 代码前缀
     * @param suffix 代码后缀
     * @return 生成的代码
     */
    public static String getCode(String prefix, String suffix) {
        String time = DateTime.now().toString("yyMMddHHmmss");
        Random r = new Random();
        int end = r.nextInt(500) + 10;
        return  prefix + time + suffix + end;
    }

    /**
     * 重载{@link #getCode(String, String)}方法，仅使用前缀生成代码。
     *
     * @param prefix 代码前缀
     * @return 生成的代码
     */
    public static String getCode(String prefix) {
        return getCode(prefix, "");
    }

    /**
     * 重载{@link #getCode(String)}方法，不使用前后缀生成代码。
     *
     * @return 生成的代码
     */
    public static String getCode() {
        return getCode("");
    }


    /**
     * 加密解密方法。
     * <p>该方法使用DES算法对字符串进行加密或解密。根据operation参数决定是加密还是解密。
     * 加密时，如果字符串长度小于8，在字符串前添加密钥。解密时，如果解密后的字符串包含密钥，则移除密钥。
     *
     * @param str 待加密解密的字符串
     * @param operation 加密解密操作标识，'E'为加密，'D'为解密
     * @return 加密解密后的字符串
     * @throws RuntimeException 如果加密或解密过程中发生异常，将抛出RuntimeException
     */
    private static String myEncrypt(String str, char operation) {
        String ENCRYPT_KEY = "encrypt-";
        DES des = new DES(Mode.CBC, Padding.PKCS5Padding, "enDeSign".getBytes(), "PrivateK".getBytes());
        String res = str;
        if (operation == 'E') {
            if (str.getBytes().length < 8) {
                str = ENCRYPT_KEY + str;
            }
            try {
                res = des.encryptHex(str);
            }catch (Exception ex){
                throw new RuntimeException("加密失败|" + ex.getMessage());
            }
        } else if (operation == 'D'){
            try {
                res = des.decryptStr(str);
                if (res.contains(ENCRYPT_KEY)) {
                    res = res.substring(ENCRYPT_KEY.length());
                }
            }catch (Exception ex) {
                throw new RuntimeException("解密失败|" + ex.getMessage() );
            }
        }
        return res;
    }

    /**
     * 加密方法。
     * <p>该方法调用{@link #myEncrypt(String, char)}方法执行加密操作。
     *
     * @param str 待加密的字符串
     * @return 加密后的字符串
     * @throws RuntimeException 如果加密过程中发生异常，将抛出RuntimeException
     */
    public static String en(String str){
        return myEncrypt(str, 'E');
    }

    /**
     * 解密方法。
     * <p>该方法调用{@link #myEncrypt(String, char)}方法执行解密操作。
     *
     * @param str 待解密的字符串
     * @return 解密后的字符串
     * @throws RuntimeException 如果解密过程中发生异常，将抛出RuntimeException
     */
    public static String de(String str){
        return myEncrypt(str, 'D');
    }

    /**
     * 将字符串转换为指定类型的对象。
     * <p>该方法首先将字符串解析为{@link JSONObject}对象，然后将其转换为指定类型的对象。
     *
     * @param bean 字符串表示的对象
     * @param T 指定的类型
     * @return 转换后的对象
     */
    public static <T> T  strToBean(String bean, Class<T> T) {
        try {
            // 将字符串转换为JSONObject对象
            JSONObject obj = JSONUtil.parseObj(bean);
            // 将JSONObject对象转换为指定类型的对象并返回
            return obj.toBean(T);
        }catch (Exception e){
            log.error("beanToStr error: { }", e);
            return null;
        }
    }

    /**
     * 将对象转换为字符串。
     * <p>
     *     该方法根据传入对象的类型，将其转换为相应的字符串表示形式。
     *     如果对象为null，则返回null。
     *     如果对象为基本数据类型（如int、long、boolean等），则直接转换为字符串。
     *     如果对象无法转换为json字符串，则返回对象的toString();
     * </p>
     *
     * @param bean 待转换的对象
     * @return 转换后的字符串 是一个标准的JSON字符串
     */
    public static String beanToStr(Object bean) {
        // 处理null值
        if (bean == null) {
            return null;
        }
        // 处理String类型
        if (bean instanceof String) {
            return (String) bean;
        }
        // 处理基本数据类型及其包装类
        if (bean instanceof Boolean || bean instanceof Number || bean instanceof Character) {
            return bean.toString();
        }
        try {
            JSONObject json = JSONUtil.parseObj(bean, false);
            return json.toString();
        } catch (Exception e) {
            // 捕获异常，记录详细信息
            log.error("beanToStr unexpected error: {}", e.getMessage(), e);
        }
        // 如果无法转换为JSON字符串，则返回对象的toString()
        return bean.toString();
    }


    /**
     * 密码加密
     * <p>
     * 该方法用于将密码进行加密处理。它使用MD5和SHA-256加密算法，结合盐值对原始密码进行两次加密，
     * 以增强密码的安全性，防止密码被轻易破解。
     *
     * @param pass 密码
     * @param salt 盐值
     * @return 加密后的密码
     */
    public static String password(String pass, String salt) {
        // 处理密码加密
        Digester md5 = new Digester(DigestAlgorithm.MD5);
        String digestHex = md5.digestHex(pass + salt);
        digestHex = md5.digestHex(digestHex);
        Digester sha = new Digester(DigestAlgorithm.SHA256);
        digestHex = sha.digestHex(digestHex);
        return digestHex;
    }

    /**
     * 生成一个登录记录码
     * <p>
     *     可逆向获取登录用户编码
     * </p>
     *
     * @param code 登录用户的编码
     * @param salt 盐值
     * @return 登录记录码
     */
    public static String loginCode(String code, String salt) {
        return en(code + salt);
    }
    /**
     * 从登录记录码中提取登录用户的编码
     *
     * @param code 登录记录码
     * @param salt 盐值
     * @return 登录用户的编码
     */
    public static String extractLoginCode(String code, String salt) {
        return de(code).replace(salt, "");
    }

    /**
     * 校验两个json字符中键数量是否相等,并且存在一一对应
     */
    public static boolean checkJsonLength(String json1, String json2) {
        if (json1 == null || json2 == null || json1.isEmpty() || json2.isEmpty())
        {
            return false;
        }
        try {
            JSONObject obj1 = JSONUtil.parseObj(json1);
            JSONObject obj2 = JSONUtil.parseObj(json2);
            if (obj1.size() != obj2.size())
            {
                return false;
            }
            for (String key : obj1.keySet())
            {
                if (!obj2.containsKey(key))
                {
                    return false;
                }
            }
            return true;
        }catch (Exception e) {
            log.error("checkJsonLength error: { }", e);
            return false;
        }
    }

    /**
     * 获取json字符串中某个键对应的值，如果存在，则返回该值，否则返回默认值
     */
    public static String parseJsonAndGetStr(String json, String key, String defaultValue) {
        // 1. 输入校验：确保 json 不为 null 或空字符串
        if (json == null || json.trim().isEmpty()) {
            return defaultValue;
        }

        try {
            // 2. 解析 JSON 对象
            JSONObject obj = JSONUtil.parseObj(json);

            // 3. 获取指定 key 的值，若不存在或为 null，则返回 defaultValue
            String result = obj.getStr(key);
            return (result != null && !result.isEmpty()) ? result : defaultValue;
        } catch (Exception e) {
            // 4. 异常处理：捕获解析异常，返回 defaultValue 或记录日志
            log.error("parseJsonAndGetStr error: { }", e);
            return defaultValue;
        }
    }
    /**
     * 将json字符串转化为map集合
     */
    public static Map<String, String> parseJsonToMap(String json) {
        Map<String, String> res = new HashMap<>();
        // 1. 输入校验：确保 json 不为 null 或空字符串
        if (json == null || json.trim().isEmpty()) {
            return res;
        }

        try {
            // 2. 解析 JSON 对象
            JSONObject obj = JSONUtil.parseObj(json);
            for (String key : obj.keySet()) {
                String value = obj.getStr(key);
                res.put(key, value);
            }
            return res;
        } catch (Exception e) {
            // 4. 异常处理：捕获解析异常，返回 defaultValue 或记录日志
            log.error("parseJsonToMap error: { }", e);
            return res;
        }
    }
}
